Flutter Android Hybrid composition 实现原理
Hybrid composition 是《Flutter 内嵌原生视图能力》在 Android 侧的第二代实现。在本文中,将介绍它的实现原理。
在 iOS 下使用 Hybrid Composition,采用另外一套写法,基于 Flutter 的 UiKitView,具体可参见 Hybrid Composition · flutter/flutter Wiki。本文介绍 Android 端实现原理。
Hybrid composition 的实现原理比 Flutter Android Virtual Display 实现原理 要复杂一些,原因是 Hybrid composition 模式大量耦合 Flutter Engine 渲染流水线,流水线的相当一部分都是在对 Hybrid composition PlatformView 做处理。因此,要捋清 Hybrid composition 的原理,就要捋清 Flutter 渲染流水线。
Step1:PlatformViewLink
在 Flutter 侧,开发者直接打交道的组件是 PlatformViewLink:
class FooPlatformView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PlatformViewLink(
viewType: 'webview',
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: gestureRecognizers,
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: 'webview',
layoutDirection: TextDirection.ltr,
creationParams: params,
creationParamsCodec: StandardMessageCodec()
)..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
}
);
}
}
PlatformViewLink 组件的最底层 Render Object 是 PlatformViewRenderBox。
Step2:渲染流水线 BeginFrame
下面进入 Flutter 渲染流水线 部分。Hybrid composition PlatformView 的绘制是从 BeginFrame 开始的,但这里的 BeginFrame,并不是 Flutter 渲染流水线 中《Step3:BeginFrame 开启新的一帧》中所说的 BeginFrame。
实际上,Hybrid composition PlatformView 的 BeginFrame 对应于Flutter 渲染流水线 中的《Step7-1 Surface 绘制》。这一步,已经是 Flutter UI 流水线渲染完成,将 Display List 传给 Rasterizer 进行光栅化绘制了。
在 DrawToSurface 这一步,准备往 Surface 上绘制时:
// Rasterizer::DrawToSurfaceUnsafe
RasterStatus Rasterizer::DrawToSurfaceUnsafe(
FrameTimingsRecorder& frame_timings_recorder,
flutter::LayerTree& layer_tree) {
SkCanvas* embedder_root_canvas = nullptr;
// Hybrid Composition 下,PlatformView 的帧绘制是从这里开始的
if (external_view_embedder_) {
external_view_embedder_->BeginFrame(
layer_tree.frame_size(), surface_->GetContext(),
layer_tree.device_pixel_ratio(), raster_thread_merger_);
embedder_root_canvas = external_view_embedder_->GetRootCanvas();
}
// On Android, the external view embedder deletes surfaces in `BeginFrame`.
//
// Deleting a surface also clears the GL context. Therefore, acquire the
// frame after calling `BeginFrame` as this operation resets the GL context.
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
if (frame == nullptr) {
return RasterStatus::kFailed;
}
// If the external view embedder has specified an optional root surface, the
// root surface transformation is set by the embedder instead of
// having to apply it here.
SkMatrix root_surface_transformation =
embedder_root_canvas ? SkMatrix{} : surface_->GetRootTransformation();
auto root_surface_canvas =
embedder_root_canvas ? embedder_root_canvas : frame->SkiaCanvas();
// 帧合成器
auto compositor_frame = compositor_context_->AcquireFrame(
surface_->GetContext(), // skia GrContext
root_surface_canvas, // root surface canvas
external_view_embedder_.get(), // external view embedder
root_surface_transformation, // root surface transformation
true, // instrumentation enabled
frame->framebuffer_info()
.supports_readback, // surface supports pixel reads
raster_thread_merger_ // thread merger
);
if (compositor_frame) {
compositor_context_->raster_cache().PrepareNewFrame();
//...
// 光栅化
RasterStatus raster_status =
compositor_frame->Raster(layer_tree, false, &damage);
if (raster_status == RasterStatus::kFailed ||
raster_status == RasterStatus::kSkipAndRetry) {
return raster_status;
}
SurfaceFrame::SubmitInfo submit_info;
submit_info.frame_damage = damage.GetFrameDamage();
submit_info.buffer_damage = damage.GetBufferDamage();
frame->set_submit_info(submit_info);
// 上屏!
if (external_view_embedder_ &&
(!raster_thread_merger_ || raster_thread_merger_->IsMerged())) {
external_view_embedder_->SubmitFrame(surface_->GetContext(),
std::move(frame));
} else {
frame->Submit();
}
return raster_status;
}
return RasterStatus::kFailed;
}
其中核心分为两步:
external_view_embedder_->BeginFrame
:Hybrid Composition 下,PlatformView 的帧绘制是从这里开始的external_view_embedder_->SubmitFrame
:如果是 Hybrid Composition PlatformView 模式下,走external_view_embedder_
的上屏逻辑
Step3:AndroidExternalViewEmbedder BeginFrame
// AndroidExternalViewEmbedder
void AndroidExternalViewEmbedder::BeginFrame(
SkISize frame_size,
GrDirectContext* context,
double device_pixel_ratio,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
Reset();
// ...
surface_pool_->SetFrameSize(frame_size);
// JNI method must be called on the platform thread.
if (raster_thread_merger->IsOnPlatformThread()) {
// 通过 JNI 调到 Java 侧
jni_facade_->FlutterViewBeginFrame();
}
frame_size_ = frame_size;
device_pixel_ratio_ = device_pixel_ratio;
}
// platform_view_android_jni_impl.cc
void PlatformViewAndroidJNIImpl::FlutterViewBeginFrame() {
JNIEnv* env = fml::jni::AttachCurrentThread();
auto java_object = java_object_.get(env);
env->CallVoidMethod(java_object.obj(), g_on_begin_frame_method);
}
// FlutterJNI.java
@SuppressWarnings("unused")
@UiThread
public void onBeginFrame() {
// ...
platformViewsController.onBeginFrame();
}
// PlatformViewsController
public void onBeginFrame() {
// 储存覆盖在 Platform View 之上的 FlutterUI
currentFrameUsedOverlayLayerIds.clear();
// 储存当前帧的 Platform View
currentFrameUsedPlatformViewIds.clear();
}
其中,主要看到是做了一些清理。
Step4-1 光栅化准备 Preroll
在Flutter 渲染流水线的《Step7-2 光栅化》步光栅化阶段,还有两步:
layer_tree.Preroll
view_embedder_->PostPrerollAction
先看 Preroll。如果在 LayerTree 中出现 Hybrid Composition 的 Layer,对应的是 PlatformViewLayer:
void PlatformViewLayer::Preroll(PrerollContext* context,
const SkMatrix& matrix) {
set_paint_boundsMakeXYWH(offset_.x(), offset_.y(), size_.width(,
size_.height()));
context->has_platform_view = true;
set_subtree_has_platform_view(true);
std::unique_ptr<EmbeddedViewParams> params =
std::make_unique<EmbeddedViewParams>(matrix, size_,
context->mutators_stack);
context->view_embedder->PrerollCompositeEmbeddedView(view_id_,
std::move(params));
}
void AndroidExternalViewEmbedder::PrerollCompositeEmbeddedView(
int view_id,
std::unique_ptr<EmbeddedViewParams> params) {
//...
// 用于创建 RTree 的工厂
auto rtree_factory = RTreeFactory();
// view_rtrees_ 存放 ViewId 对应的 RTree
view_rtrees_.insert_or_assign(view_id, rtree_factory.getInstance());
// `SkPictureRecorder`是Skia图形库中的一个类,它用于记录一系列的绘图命令,
// 然后可以将这些命令保存到一个`SkPicture`对象中,以便在以后的时间点重播(replay)这些命令。
auto picture_recorder = std::make_unique<SkPictureRecorder>();
// 开始记录绘图操作
// frame_size_:绘图区域
// rtree_factory:RTree
picture_recorder->beginRecordingMake(frame_size_), &rtree_factory;
// picture_recorders_ 存放 ViewId 对应的 SkPictureRecorder
picture_recorders_.insert_or_assign(view_id, std::move(picture_recorder));
// 将视图ID添加到`composition_order_`列表的末尾。
composition_order_.push_back(view_id);
// 检查`view_params_`映射中是否已经存在相同的视图参数,如果存在并且参数没有改变,那么就直接返回。
// Update params only if they changed.
if (view_params_.count(view_id) == 1 &&
view_params_.at(view_id) == *params.get()) {
return;
}
// 将视图参数插入到`view_params_`映射中
view_params_.insert_or_assign(view_id, EmbeddedViewParams(*params.get()));
}
在上面代码中,会将当前平台视图加入到 RTree 中,并且创建 SkPictureRecorder。
Step4-2 光栅化准备 PostPrerollAction
主要是与线程合并相关。
PostPrerollResult AndroidExternalViewEmbedder::PostPrerollAction(
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
if (!raster_thread_merger->IsMerged()) {
// The raster thread merger may be disabled if the rasterizer is being
// created or teared down.
//
// In such cases, the current frame is dropped, and a new frame is attempted
// with the same layer tree.
//
// Eventually, the frame is submitted once this method returns `kSuccess`.
// At that point, the raster tasks are handled on the platform thread.
CancelFrame();
raster_thread_merger->MergeWithLease(kDefaultMergedLeaseDuration);
return PostPrerollResult::kSkipAndRetryFrame;
}
raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
// Surface switch requires to resubmit the frame.
// TODO(egarciad): https://github.com/flutter/flutter/issues/65652
if (previous_frame_view_count_ == 0) {
return PostPrerollResult::kResubmitFrame;
}
return PostPrerollResult::kSuccess;
}
Step5:上屏
这个方法的主要作用是提交一个帧,帧中可能包含多个平台视图(例如Android的View或Widget)和Flutter的UI。
这个方法的核心原理是使用Skia的绘图命令和RTree数据结构来处理平台视图和Flutter UI的交互。当Flutter UI与平台视图相交时,会创建一个覆盖层,这个覆盖层会覆盖在平台视图上,从而实现Flutter UI和平台视图的混合渲染。
如何理解 SubmitFrame?
在Flutter引擎中,"SubmitFrame"是一个重要的概念,它是渲染流程的一部分。当Flutter引擎完成一帧的绘制后,它会调用"SubmitFrame"方法将这一帧提交给底层的图形API(例如OpenGL或Vulkan),然后这一帧就会被显示在屏幕上。
整体流程:
-
检查帧是否包含平台层:如果帧不包含任何平台层,那么直接提交帧并返回。
-
初始化变量:创建了几个用于存储视图信息的映射和列表,获取了帧的背景画布,保存了当前帧的视图数量。
-
保存和恢复画布状态:使用
SkAutoCanvasRestore
对象保存画布的状态,当对象销毁时(即方法返回时),画布的状态会自动恢复。 -
遍历所有视图:对于每一个视图,首先结束其绘图记录,获取其绘图命令和RTree(一个用于快速查询矩形交集的数据结构)。然后,检查这个视图的Flutter UI是否与之前的任何平台视图相交,如果相交,那么就将相交的区域添加到
overlay_layers
映射中,并从背景画布中剪切掉这个区域。最后,将视图的绘图命令绘制到背景画布上。 -
提交背景画布:如果在上一帧中有视图,那么就提交背景画布。
-
显示平台视图:对于每一个视图,首先获取其位置和大小,然后调用
FlutterViewOnDisplayPlatformView
方法显示或更新视图。如果视图有一个覆盖层,那么就创建一个新的表面(如果需要的话),并提交这个表面。
具体代码实现:
这里面的 View 和 ViewId 不只是平台视图的,也包括 Flutter UI 的
void AndroidExternalViewEmbedder::SubmitFrame(
GrDirectContext* context,
std::unique_ptr<SurfaceFrame> frame) {
//...
// 如果帧不包含任何平台层,那么直接提交帧并返回。
if (!FrameHasPlatformLayers()) {
frame->Submit();
return;
}
std::unordered_map<int64_t, SkRect> overlay_layers;
std::unordered_map<int64_t, sk_sp<SkPicture>> pictures;
SkCanvas* background_canvas = frame->SkiaCanvas();
auto current_frame_view_count = composition_order_.size();
// 使用`SkAutoCanvasRestore`对象保存画布的状态,当对象销毁时(即方法返回时),画布的状态会自动恢复。
SkAutoCanvasRestore save(background_canvas, /*doSave=*/true);
// 遍历所有视图,具体指所有使用 HybridComposition 模式的平台视图
for (size_t i = 0; i < current_frame_view_count; i++) {
int64_t view_id = composition_order_[i];
// 首先结束其绘图记录,获取其绘图命令和RTree
sk_sp<SkPicture> picture =
picture_recorders_.at(view_id)->finishRecordingAsPicture();
// ...
pictures.insert({view_id, picture});
sk_sp<RTree> rtree = view_rtrees_.at(view_id);
SkRect joined_rect = SkRect::MakeEmpty();
// 然后,检查这个视图的Flutter UI是否与之前的任何平台视图相交,如果相交,
// 那么就将相交的区域添加到`overlay_layers`映射中,并从背景画布中剪切掉这个区域。
// 最后,将视图的绘图命令绘制到背景画布上。
for (ssize_t j = i; j >= 0; j--) {
int64_t current_view_id = composition_order_[j];
SkRect current_view_rect = GetViewRect(current_view_id);
// Each rect corresponds to a native view that renders Flutter UI.
// 计算是否有相交
std::list<SkRect> intersection_rects =
rtree->searchNonOverlappingDrawnRects(current_view_rect);
// Limit the number of native views, so it doesn't grow forever.
// 优化手段
// 这段代码的作用是将多个矩形区域合并为一个大的矩形区域,这个大的矩形区域是所有小矩形区域的并集。
// 如果多个平台视图占用的屏幕位置是重合的话,会在原生侧复用一个 Overlay
for (const SkRect& rect : intersection_rects) {
joined_rect.join(rect);
}
}
// 如果存在重合区域
if (!joined_rect.isEmpty()) {
// Subpixels in the platform may not align with the canvas subpixels.
//
// To workaround it, round the floating point bounds and make the rect
// slightly larger.
//
// For example, {0.3, 0.5, 3.1, 4.7} becomes {0, 0, 4, 5}.
// 这行代码将`joined_rect`的边界四舍五入到最接近的整数,并稍微扩大这个矩形区域,以确保它包含原始的浮点数边界。
// `roundOut`方法返回一个新的矩形,这个矩形的边界是四舍五入并扩大后的结果。
joined_rect.set(joined_rect.roundOut());
// 注册到 overlay_layers
overlay_layers.insert({view_id, joined_rect});
// 码从背景画布中剪切掉`joined_rect`区域。
background_canvas->clipRect(joined_rect, SkClipOp::kDifference);
}
// `drawPicture`是Skia图形库中`SkCanvas`类的一个方法,它接受一个`SkPicture`对象作为参数,
// 然后在画布上按照`SkPicture`对象记录的绘图命令进行绘制。
// 将图像绘制到 background_canvas
// 将 UI 绘制到背景层上
background_canvas->drawPicture(pictures.at(view_id));
}
// 在切换到覆盖层表面的OpenGL上下文之前,提交背景画布帧。
// 说明了如果嵌入正在切换表面,那么应该跳过一帧,并在`PostPrerollAction`中指示这一帧必须被重新提交。
auto should_submit_current_frame = previous_frame_view_count_ > 0;
if (should_submit_current_frame) {
frame->Submit();
}
for (int64_t view_id : composition_order_) {
SkRect view_rect = GetViewRect(view_id);
const EmbeddedViewParams& params = view_params_.at(view_id);
// Display the platform view. If it's already displayed, then it's
// just positioned and sized.
// 通知原生侧展示视图
jni_facade_->FlutterViewOnDisplayPlatformView(
view_id, //
view_rect.x(), //
view_rect.y(), //
view_rect.width(), //
view_rect.height(), //
params.sizePoints().width() * device_pixel_ratio_,
params.sizePoints().height() * device_pixel_ratio_,
params.mutatorsStack() //
);
// 为每个视图创建一个表面(如果需要的话),并在需要提交当前帧时提交这个表面。
std::unordered_map<int64_t, SkRect>::const_iterator overlay =
overlay_layers.find(view_id);
if (overlay == overlay_layers.end()) {
continue;
}
// 为视图创建一个表面
// 这里的视图,指的是需要绘制在原生视图上面的 FLutter UI
std::unique_ptr<SurfaceFrame> frame =
CreateSurfaceIfNeeded(context, //
view_id, //
pictures.at(view_id), //
overlay->second //
);
// 如果需要提交当前帧,那么就调用`frame`对象的`Submit`方法,提交这个表面。
if (should_submit_current_frame) {
frame->Submit();
}
}
}
对于 background_canvas->drawPicture(pictures.at(view_id));
理解:
-
在Flutter的Hybrid Composition模式下,
pictures
映射存储的是每个PlatformView的Flutter UI部分的绘图命令,而不是PlatformView本身。这些绘图命令被封装在SkPicture
对象中。 -
当Flutter引擎渲染一帧时,它会为每个PlatformView创建一个
SkPictureRecorder
对象,然后在这个对象上执行绘图命令。当绘图完成后,SkPictureRecorder
对象会生成一个SkPicture
对象,这个对象包含了所有的绘图命令。 -
background_canvas->drawPicture(pictures.at(view_id));
这行代码的作用是将SkPicture
对象中的绘图命令回放在背景画布上,从而在背景画布上绘制出Flutter UI部分的图像。 -
需要注意的是,这里绘制的只是Flutter UI部分的图像,而不是PlatformView本身。PlatformView的内容是由原生平台(例如Android)负责渲染的,Flutter引擎只负责将Flutter UI部分的图像和PlatformView的内容合成在一起,形成最终的图像。
对于 composition_order_
的理解:
-
composition_order_
是一个包含视图ID的列表,这些视图ID的顺序决定了视图的合成顺序。也就是说,这个列表中的视图ID的顺序决定了这些视图在屏幕上的显示顺序。 -
在Flutter中,视图的合成顺序非常重要,因为它决定了视图的堆叠顺序。在列表中靠前的视图会被绘制在靠后的视图的下面。换句话说,如果两个视图的位置有重叠,那么在
composition_order_
列表中靠后的视图会覆盖靠前的视图。 -
for (int64_t view_id : composition_order_) {
这行代码是遍历composition_order_
列表,按照列表中的顺序处理每个视图。这意味着在屏幕上,靠后的视图会被绘制在靠前的视图的上面。
Step6:通知原生展示平台视图
在 jni_facade_->FlutterViewOnDisplayPlatformView
中,通知原生侧展示原生视图。
void PlatformViewAndroidJNIImpl::FlutterViewOnDisplayPlatformView(
int view_id,
int x,
int y,
int width,
int height,
int viewWidth,
int viewHeight,
MutatorsStack mutators_stack) {
JNIEnv* env = fml::jni::AttachCurrentThread();
auto java_object = java_object_.get(env);
jobject mutatorsStack = env->NewObject(g_mutators_stack_class->obj(),
g_mutators_stack_init_method);
std::vector<std::shared_ptr<Mutator>>::const_iterator iter =
mutators_stack.Begin();
while (iter != mutators_stack.End()) {
switch ((*iter)->GetType()) {
case transform: {
// ...
break;
}
case clip_rect: {
// ...
break;
}
case clip_rrect: {
// ...
break;
}
// TODO(cyanglaz): Implement other mutators.
// https://github.com/flutter/flutter/issues/58426
case clip_path:
case opacity:
break;
}
++iter;
}
env->CallVoidMethod(java_object.obj(), g_on_display_platform_view_method,
view_id, x, y, width, height, viewWidth, viewHeight,
mutatorsStack);
}
// FlutterJNI
public void onDisplayPlatformView(
int viewId,
int x,
int y,
int width,
int height,
int viewWidth,
int viewHeight,
FlutterMutatorsStack mutatorsStack) {
ensureRunningOnMainThread();
if (platformViewsController == null) {
throw new RuntimeException(
"platformViewsController must be set before attempting to position a platform view");
}
platformViewsController.onDisplayPlatformView(
viewId, x, y, width, height, viewWidth, viewHeight, mutatorsStack);
}
// PlatformViewsController
public void onDisplayPlatformView(
int viewId,
int x,
int y,
int width,
int height,
int viewWidth,
int viewHeight,
FlutterMutatorsStack mutatorsStack) {
initializeRootImageViewIfNeeded();
initializePlatformViewIfNeeded(viewId);
final FlutterMutatorView parentView = platformViewParent.get(viewId);
parentView.readyToDisplay(mutatorsStack, x, y, width, height);
parentView.setVisibility(View.VISIBLE);
// `bringToFront`方法在Android的View类中用于将一个视图移到其父视图的前面。
// 这意味着如果在屏幕上有多个视图重叠,调用此方法的视图会显示在最上层,因此会遮挡其他所有重叠的视图。
parentView.bringToFront();
final FrameLayout.LayoutParams layoutParams =
new FrameLayout.LayoutParams(viewWidth, viewHeight);
final View view = platformViews.get(viewId).getView();
// 再把原生视图放在最上面
if (view != null) {
view.setLayoutParams(layoutParams);
view.bringToFront();
}
currentFrameUsedPlatformViewIds.add(viewId);
}
其中:FlutterMutatorsStack 是需要合成的视图栈,包括 FlutterUI 底图,PlatformViews,FlutterUI 顶图。
所谓 FlutterUI 顶图,是有些 Flutter UI 需要展示在 PlatformViews 之上。
Step7-1:FlutterView 准备1:切换至 ImageView 模式
FlutterView 支持三种显示模式(SurfaceView、TextureView、ImageView)。ImageView 模式用于展示平台视图,这里切换至 ImageView 模式。
// PlatformViewsController
private void initializeRootImageViewIfNeeded() {
if (synchronizeToNativeViewHierarchy && !flutterViewConvertedToImageView) {
flutterView.convertToImageView();
flutterViewConvertedToImageView = true;
}
}
// FlutterView
public void convertToImageView() {
renderSurface.pause();
// 这里的 ImageView 是用于展示底图 UI 部分
if (flutterImageView == null) {
flutterImageView = createImageView();
addView(flutterImageView);
} else {
flutterImageView.resizeIfNeeded(getWidth(), getHeight());
}
// 保存之前的 Surface,用于恢复
previousRenderSurface = renderSurface;
renderSurface = flutterImageView;
if (flutterEngine != null) {
renderSurface.attachToRenderer(flutterEngine.getRenderer());
}
}
public FlutterImageView createImageView() {
return new FlutterImageView(
getContext(), getWidth(), getHeight(), FlutterImageView.SurfaceKind.background);
}
Step7-2:FlutterView 准备2:PlatformView 准备
PlatformView 是早已经创建好的,还有另一路 Channel 路径。我们前面分析的是渲染流水线的路径,对 Channel 路径在后面分析,搞懂流水线,对另一条路径就是小 Case 了。
// PlatformViewsController
void initializePlatformViewIfNeeded(int viewId) {
// PlatformView 是早已经创建好的
final PlatformView platformView = platformViews.get(viewId);
if (platformView == null) {
throw new IllegalStateException(
"Platform view hasn't been initialized from the platform view channel.");
}
// ...
// 创建一个 FlutterMutatorView 用于展示图层合成堆栈
// 如果会有上层遮盖层,会再往这里面添加 ImageView
final FlutterMutatorView parentView =
new FlutterMutatorView(
context, context.getResources().getDisplayMetrics().density, androidTouchProcessor);
// 焦点设置
parentView.setOnDescendantFocusChangeListener(
(view, hasFocus) -> {
if (hasFocus) {
platformViewsChannel.invokeViewFocused(viewId);
} else if (textInputPlugin != null) {
textInputPlugin.clearPlatformViewClient(viewId);
}
});
// 将 PlatformView 添加到 FlutterMutatorView
// 将 FlutterMutatorView 添加到 FlutterView
platformViewParent.put(viewId, parentView);
parentView.addView(platformView.getView());
flutterView.addView(parentView);
}
Step7-3:FlutterView 准备3:FlutterMutatorView 准备
当需要展示平台视图时,在前面的步骤会将渲染分为三个部分:位于原生视图之下的 Flutter 背景 UI、位于中间层的原生视图,以及盖在原生视图上的 FlutterUI。FlutterMutatorView 是一个布局容器,负责容纳后面两者。
// 配置偏移量
public void readyToDisplay(
@NonNull FlutterMutatorsStack mutatorsStack, int left, int top, int width, int height) {
this.mutatorsStack = mutatorsStack;
this.left = left;
this.top = top;
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
layoutParams.leftMargin = left;
layoutParams.topMargin = top;
setLayoutParams(layoutParams);
setWillNotDraw(false);
}
接下来让 FlutterMutatorView 可见并位于最上层。
Step8:前景上屏
上面步骤完成了 Flutter UI 底图和 PlatformView 的上屏,但是还会有覆盖在 PlatformView 上面的 Flutter UI。它们也需要创建对应的 FlutterImageView 进行展示。这一步,由《Step7 rasterizer 绘制》draw 方法最后的这一部分来驱动:
// EndFrame should perform cleanups for the external_view_embedder.
if (surface_ && external_view_embedder_) {
external_view_embedder_->EndFrame(should_resubmit_frame,
raster_thread_merger_);
}
// AndroidExternalViewEmbedder
void AndroidExternalViewEmbedder::EndFrame(
bool should_resubmit_frame,
fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
surface_pool_->RecycleLayers();
// JNI method must be called on the platform thread.
if (raster_thread_merger->IsOnPlatformThread()) {
jni_facade_->FlutterViewEndFrame();
}
}
// platform_view_android_jni_impl.cc
void PlatformViewAndroidJNIImpl::FlutterViewEndFrame() {
JNIEnv* env = fml::jni::AttachCurrentThread();
auto java_object = java_object_.get(env);
if (java_object.is_null()) {
return;
}
env->CallVoidMethod(java_object.obj(), g_on_end_frame_method);
}
// FlutterJNI
@SuppressWarnings("unused")
@UiThread
public void onEndFrame() {
ensureRunningOnMainThread();
if (platformViewsController == null) {
throw new RuntimeException(
"platformViewsController must be set before attempting to end the frame");
}
platformViewsController.onEndFrame();
}
// PlatformViewsController
public void onEndFrame() {
// If there are no platform views in the current frame,
// then revert the image view surface and use the previous surface.
//
// Otherwise, acquire the latest image.
if (flutterViewConvertedToImageView && currentFrameUsedPlatformViewIds.isEmpty()) {
flutterViewConvertedToImageView = false;
// 状态切换,从 ImageView 切回去
flutterView.revertImageView(
() -> {
// Destroy overlay surfaces once the surface reversion is completed.
finishFrame(false);
});
return;
}
// Whether the current frame was rendered using ImageReaders.
//
// Since the image readers may not have images available at this point,
// this becomes true if all the required surfaces have images available.
//
// This is used to decide if the platform views can be rendered in the current frame.
// If one of the surfaces doesn't have an image, the frame may be incomplete and must be
// dropped.
// For example, a toolbar widget painted by Flutter may not be rendered.
final boolean isFrameRenderedUsingImageReaders =
flutterViewConvertedToImageView && flutterView.acquireLatestImageViewFrame();
finishFrame(isFrameRenderedUsingImageReaders);
}
// PlatformViewsController
private void finishFrame(boolean isFrameRenderedUsingImageReaders) {
// 遍历覆盖在上面的 Flutter UI
for (int i = 0; i < overlayLayerViews.size(); i++) {
final int overlayId = overlayLayerViews.keyAt(i);
// 取出对应的覆盖 FlutterImageView
final FlutterImageView overlayView = overlayLayerViews.valueAt(i);
// 遍历的是所有的,看当前帧里是否包含,如果包含的上屏展示,不包含的,隐藏
if (currentFrameUsedOverlayLayerIds.contains(overlayId)) {
flutterView.attachOverlaySurfaceToRender(overlayView);
final boolean didAcquireOverlaySurfaceImage = overlayView.acquireLatestImage();
isFrameRenderedUsingImageReaders &= didAcquireOverlaySurfaceImage;
} else {
// If the background surface isn't rendered by the image view, then the
// overlay surfaces can be detached from the rendered.
// This releases resources used by the ImageReader.
if (!flutterViewConvertedToImageView) {
overlayView.detachFromRenderer();
}
// Hide overlay surfaces that aren't rendered in the current frame.
overlayView.setVisibility(View.GONE);
}
}
// 再遍历一下 MutatorView,把这些 ImageView 展示出来
for (int i = 0; i < platformViewParent.size(); i++) {
final int viewId = platformViewParent.keyAt(i);
final View parentView = platformViewParent.get(viewId);
// This should only show platform views that are rendered in this frame and either:
// 1. Surface has images available in this frame or,
// 2. Surface does not have images available in this frame because the render surface should
// not be an ImageView.
//
// The platform view is appended to a mutator view.
//
// Otherwise, hide the platform view, but don't remove it from the view hierarchy yet as
// they are removed when the framework diposes the platform view widget.
if (currentFrameUsedPlatformViewIds.contains(viewId)
&& (isFrameRenderedUsingImageReaders || !synchronizeToNativeViewHierarchy)) {
parentView.setVisibility(View.VISIBLE);
} else {
parentView.setVisibility(View.GONE);
}
}
}
其中:flutterView.attachOverlaySurfaceToRender(overlayView);
将会触发 FlutterView 中的:
public void attachOverlaySurfaceToRender(FlutterImageView view) {
if (flutterEngine != null) {
view.attachToRenderer(flutterEngine.getRenderer());
}
}
这样,覆盖在 PlatformView 上面的 Flutter UI 也展示出来了。
第二部分:Native 视图的创建
上面的分析过程,相当于 Hybrid composition 的渲染主线,但前面说到,具体的原生视图,在流水线渲染时已经提前创建好了。从本节开始,我们来分析另一条分支,原生视图的创建。让我们回到最开始的创建:
class FooPlatformView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PlatformViewLink(
viewType: 'webview',
onCreatePlatformView: createFooWebView,
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: gestureRecognizers,
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: 'webview',
layoutDirection: TextDirection.ltr,
creationParams: params,
creationParamsCodec: StandardMessageCodec()
)..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
// 关键方法
..create();
}
);
}
}
其中的 create 方法比较关键。
initSurfaceAndroidView
我们先看 PlatformViewsService.initSurfaceAndroidView
方法的实现,创建了一个 SurfaceAndroidViewController 实例:
static SurfaceAndroidViewController initSurfaceAndroidView({
required int id,
required String viewType,
required TextDirection layoutDirection,
dynamic creationParams,
MessageCodec<dynamic>? creationParamsCodec,
VoidCallback? onFocus,
}) {
final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
viewId: id,
viewType: viewType,
layoutDirection: layoutDirection,
creationParams: creationParams,
creationParamsCodec: creationParamsCodec,
);
_instance._focusCallbacks[id] = onFocus ?? () {};
return controller;
}
接下来回调上一节说到的 create。
SurfaceAndroidViewController 的 create
SurfaceAndroidViewController 继承自 AndroidViewController,SurfaceAndroidViewController 本身没有覆写 create,因此首先调用 AndroidViewController 的 create 方法:
// AndroidViewController
Future<void> create() async {
assert(_state != _AndroidViewState.disposed, 'trying to create a disposed Android view');
await _sendCreateMessage();
_state = _AndroidViewState.created;
for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) {
callback(viewId);
}
}
其中,_sendCreateMessage
由 SurfaceAndroidViewController 实现,从这一步开始,开始通知 Native 创建原生视图:
@override
Future<void> _sendCreateMessage() {
final Map<String, dynamic> args = <String, dynamic>{
'id': viewId,
'viewType': _viewType,
'direction': AndroidViewController._getAndroidDirection(_layoutDirection),
'hybrid': true,
};
if (_creationParams != null) {
final ByteData paramsByteData =
_creationParamsCodec!.encodeMessage(_creationParams)!;
args['params'] = Uint8List.view(
paramsByteData.buffer,
0,
paramsByteData.lengthInBytes,
);
}
return SystemChannels.platform_views.invokeMethod<void>('create', args);
}
会调用 Channel,调用原生侧。
PlatformViewsChannel.create
这一步与《Flutter Android Virtual Display 实现原理》的《Flutter PlatformViewsChannel》一节是一致的。
在 Android 部分,MethodCallHandler.create 负责处理 Channel 请求,具体可参见《MethodCallHandler.create》方法。
区别在于,此处将走 usesHybridComposition 的分支,即调用 createAndroidViewForPlatformView
。
createAndroidViewForPlatformView
该方法的实现位于 PlatformViewsController 中的 channelHandler,这是一个匿名内部类实例,其中包括对 createAndroidViewForPlatformView 方法的实现。
// PlatformViewsController
public void createAndroidViewForPlatformView(
@NonNull PlatformViewsChannel.PlatformViewCreationRequest request) {
// API level 19 is required for `android.graphics.ImageReader`.
ensureValidAndroidVersion(Build.VERSION_CODES.KITKAT);
//...
final PlatformViewFactory factory = registry.getFactory(request.viewType);
//...
Object createParams = null;
if (request.params != null) {
createParams = factory.getCreateArgsCodec().decodeMessage(request.params);
}
final PlatformView platformView = factory.create(context, request.viewId, createParams);
platformView.getView().setLayoutDirection(request.direction);
platformViews.put(request.viewId, platformView);
}
可以看到,在这一步直接创建原生视图,并且将该视图放入 platformViews 中备用,在前文的渲染流水线主分支中,将从中取出创建好的视图,直接进行混合展示。
附录
PlatformViewLink 初始化
PlatformViewLink 本身是一个 StatefulWidget,在 State 中有一个初始化方法:
// _PlatformViewLinkState
class _PlatformViewLinkState extends State<PlatformViewLink> {
@override
void initState() {
_focusNode = FocusNode(debugLabel: 'PlatformView(id: $_id)');
_initialize();
super.initState();
}
void _initialize() {
// 从 Flutter 侧创建一个唯一标识
_id = platformViewsRegistry.getNextPlatformViewId();
// 调用外界传入的 _onCreatePlatformView
_controller = widget._onCreatePlatformView(
PlatformViewCreationParams._(
id: _id!,
viewType: widget.viewType,
onPlatformViewCreated: _onPlatformViewCreated,
onFocusChanged: _handlePlatformFocusChanged,
),
);
}
// ...
}
结合上一节来看,_onCreatePlatformView
是在 PlatformViewLink 创建时传入的:
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: 'webview',
layoutDirection: TextDirection.ltr,
creationParams: params,
creationParamsCodec: StandardMessageCodec()
)..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
}
由 PlatformViewsService.initSurfaceAndroidView
创建出一个 PlatformViewController 实例,存在 _PlatformViewLinkState
进行使用。
SurfaceAndroidViewController 的使用
接下来我们回到 _PlatformViewLinkState
中,分析 SurfaceAndroidViewController 是如何被使用的。
在 _PlatformViewLinkState
的 build 方法中,回调了 surfaceFactory,并向其中传入了 _controller
:
@override
Widget build(BuildContext context) {
if (!_platformViewCreated) {
return const SizedBox.expand();
}
_surface ??= widget._surfaceFactory(context, _controller!);
return Focus(
focusNode: _focusNode,
onFocusChange: _handleFrameworkFocusChanged,
child: _surface!,
);
}
对应于上面使用代码的:
surfaceFactory: (BuildContext context, PlatformViewController controller) {
return PlatformViewSurface(
gestureRecognizers: gestureRecognizers,
controller: controller,
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
在 PlatformViewSurface
中,又进一步将 controller 传入 PlatformViewRenderBox:
// PlatformViewSurface
@override
RenderObject createRenderObject(BuildContext context) {
return PlatformViewRenderBox(controller: controller, gestureRecognizers: gestureRecognizers, hitTestBehavior: hitTestBehavior);
}
继续进入 PlatformViewRenderBox
,这是一个 RenderObject,在 paint 方法中,会添加一个 PlatformViewLayer。
Flutter 侧代码实现
我们按照自底向上的方式,从最底层实现开始,一层层向上,最终到达开发者熟悉的 PlatformViewLink。
《Flutter PlatformViewRenderBox》
《Flutter PlatformViewsService》
《Flutter SurfaceAndroidViewController》
C++ 侧代码实现
《Flutter AndroidExternalViewEmbedder》:基类《Flutter ExternalViewEmbedder》
本文作者:Maeiee
本文链接:Flutter Android Hybrid composition 实现原理
版权声明:如无特别声明,本文即为原创文章,版权归 Maeiee 所有,未经允许不得转载!
喜欢我文章的朋友请随缘打赏,鼓励我创作更多更好的作品!